
package view.panels.graphical.drawables;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Collection;
import java.util.List;
import java.util.TreeMap;
import java.util.Vector;
import jgf.Attribute;
import jgf.IGraph;
import jgf.beans.ArcBean;
import jgf.beans.NodeBean;
import jgf.exceptions.arc.UnexistentArcException;
import jgf.exceptions.node.UnexistentNodeException;
import jgf.lists.GenericGraph;

/**
 *
 * @author Marcos Garcia Ortega <a href="mailto:thiamath@gmail.com">&lt;thiamath@gmail.com&gt;</a>
 */
public class DrawGraph implements Drawable {

	private Integer ID;
	private String name;
	private Point center;
	private float spread;
	private TreeMap<Integer, DrawNode> drawNodes;
	private TreeMap<Integer, DrawArc> drawArcs;
	private DrawArc floatingArc;
	private int type;

	/**
	 * Creates a drawable graph from a graph. It is able to distinguish between Graphs, Digraphs....
	 * @param graph The template graph to create the drawable graph.
	 */
	public DrawGraph(IGraph graph) {
		center = new Point(0, 0);//sets where will be the center of the displayed graph (required).
		ID = graph.getID();
		name = graph.getName();

		if (graph.getType() == GenericGraph.DIGRAPH) {
			type = GenericGraph.DIGRAPH;
		} else if (graph.getType() == GenericGraph.GRAPH) {
			type = GenericGraph.GRAPH;
		}

		drawNodes = new TreeMap<Integer, DrawNode>();
		for (Integer nodeID : graph.getNodeList()) {
			try {
				drawNodes.put(nodeID, new DrawNode(graph.selectNode(nodeID), new Point(0, 0)));
			} catch (UnexistentNodeException e) {
				//Never reached
			}
		}

		drawArcs = new TreeMap<Integer, DrawArc>();
		List<Integer> arcs = graph.getArcList();
		for (Integer arcID : arcs) {
			DrawArc a = null;
			try {
				if (type == IGraph.DIGRAPH) {
					a = new DrawDiarc(graph.selectArc(arcID));
				} else {
					a = new DrawArc(graph.selectArc(arcID));
				}
			} catch (UnexistentArcException e) {
				//Never reached
			}
			drawNodes.get(arcID).addLinkedArc(a);
			drawNodes.get(arcID).addLinkedArc(a);
			drawArcs.put(arcID, a);
		}

		this.reshape();
	}

	public String getName() {
		return name;
	}

	public Collection<DrawNode> getNodes() {
		return this.drawNodes.values();
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getID() {
		return ID;
	}

	public void setID(Integer id) {
		ID = id;
	}

	public List<Integer> getSelectedNodes() {
		Vector<Integer> ret = new Vector<Integer>();
		Collection<DrawNode> nodes = drawNodes.values();
		for (DrawNode drawNode : nodes) {
			if (drawNode.isSelected()) {
				ret.add(drawNode.getId());
			}
		}
		return ret;
	}

	public void select(Interactive selected) {
		selected.select();
	}

	public void deselect(Interactive deselected) {
		deselected.deselect();
	}

	public void setCenter(double x, double y) {
		center.setLocation(x, y);
	}

	public Point getCenter() {
		return center;
	}

	/**
	 * Returns the pointed object of the <code>DrawGraph</code> if any.
	 * @param point The point where seek an object.
	 * @return A <code>Interactive</code> object located at the coordinates of <b>point</b> or <code>null</code> if there is no <code>Interactive</code> objects.
	 */
	public Interactive getPointed(Point point) {
		Collection<DrawNode> dnodes = drawNodes.values();
		for (DrawNode drawNode : dnodes) {
			if (drawNode.getShape().contains(point)) {
				return drawNode;
			}
		}
		Collection<DrawArc> darcs = drawArcs.values();
		for (DrawArc drawArc : darcs) {
			// TODO hacer que se puedan seleccionar mejor los arcos del grafo.
			if (drawArc.getShape().getBounds2D().contains(point)) {
				return drawArc;
			}
		}
		return null;
	}

	/**
	 * Returns the arcs linked to the drawable node node.
	 * @param node The node where to extract the linked arcs.
	 * @return A Vector of DrawArcs with the Draw
	 */
	public Vector<DrawArc> getLinkedArcs(DrawNode node) {
		return node.getLinkedArcs();
	}

	public void display(Graphics2D g) {
		Collection<DrawArc> darcs = drawArcs.values();
		for (DrawArc drawArc : darcs) {
			drawArc.display(g);
		}
		Collection<DrawNode> dnodes = drawNodes.values();
		for (DrawNode drawNode : dnodes) {
			drawNode.display(g);
		}
		if (floatingArc != null) {
			floatingArc.display(g);
		}
	}

	public void setColor(Color color) {
		throw new UnsupportedOperationException("Not supported yet.");
	}

	public void setFloatingArc(DrawNode node) {
		switch (type) {
			case IGraph.GRAPH:
				this.floatingArc = new DrawArc(node.getCenter(), node.getCenter());
				break;
			case IGraph.DIGRAPH:
				this.floatingArc = new DrawDiarc(node.getCenter(), node.getCenter());
				break;
		}
	}

	public void moveFloatingArc(Point point) {
		if (floatingArc != null) {
			this.floatingArc.setEnd(point);
		}
	}

	public void clearFloatingArc() {
		this.floatingArc = null;
	}

	/**
	 * By now, this method will reshape the graph with a circular pattern.
	 */
	public void reshape() {
		// TODO Remodelate:
		// Lo que tiene que hacer es situar los nodos mediante un sistema de traslacion de coordenadas
		// al lugar en el que estan con respecto al centro del grafo, pero sin resetear su situacion.
		// Por lo tanto, no debe recolocar los nodos con respecto al patron circular en cada llamada a esta funcion.

		applyCircularPattern();
	}

	public void applyCircularPattern() {
		double rad = 0;//radians

		Collection<DrawNode> dnodes = drawNodes.values();
		int i = 0;//Will be the passed nodes
		for (DrawNode drawNode : dnodes) {
			rad = ((2 * Math.PI) / dnodes.size()) * i;//divide a circumference by the number of nodes and add the number of nodes already passed by.
			drawNode.setCenter((float) ((Math.cos(rad) * spread) + center.getX()),
					(float) ((Math.sin(rad) * spread) + center.getY()));//Set the center of the node with rad inclination and spread from center at the given distance in pixels.
			drawNode.setDefaultRadius();
			i++;
		}
	}


	//<editor-fold defaultstate="expanded" desc="Observer Methods">
	// TODO IMPLEMENT ALL OBSERVER METHODS
	public void deleteArc(ArcBean arc) {
		DrawArc removed = drawArcs.get(arc.getId());
		deleteArc(removed);
	}

	public void setOriginOfArc(DrawArc arc, Point point) {
		arc.setOrigin(point);
	}

	public void setEndOfArc(DrawArc arc, Point point) {
		arc.setEnd(point);
	}

	public void setSpreadFactor(int spread) {
		this.spread = spread - DrawNode.RADIUS;
	}

	private void deleteArc(DrawArc removed) {
		DrawNode from = drawNodes.get(removed.getFrom());
		DrawNode to = drawNodes.get(removed.getTo());
		Vector<DrawArc> v = from.getLinkedArcs();
		v.remove(removed);
		v = to.getLinkedArcs();
		v.remove(removed);
		drawArcs.remove(removed.getId());
	}

	public void insertArc(ArcBean arc) {
		DrawArc inserted = null;
		switch (type) {
			case IGraph.GRAPH:
				inserted = new DrawArc(arc);
				break;
			case IGraph.DIGRAPH:
				inserted = new DrawDiarc(arc);
				break;
		}
		drawArcs.put(inserted.getId(), inserted);
		DrawNode from = drawNodes.get(arc.getFromId());
		DrawNode to = drawNodes.get(arc.getToId());
		from.addLinkedArc(inserted);
		to.addLinkedArc(inserted);
	}

	public void updateArc(ArcBean arc) {
		DrawArc updated = drawArcs.get(arc.getId());
		Attribute oldOne = updated.getShowedAttribute();
		updated.setShowedAttribute(new Attribute(oldOne.getName(), arc.getAttributes().get(0).getValue()));
	}

	public void colorArc(ArcBean arc, Color color) {//TODO
//		throw new UnsupportedOperationException("Not supported yet.");
	}

	public void deleteNode(NodeBean node) {
		DrawNode removed = drawNodes.get(node.getId());
		deleteNode(removed);
	}

	private void deleteNode(DrawNode removed) {
		Vector<DrawArc> linkedArcs = (Vector<DrawArc>) removed.getLinkedArcs().clone();//get yhe arcs linked to the removed node
		for (DrawArc drawArc : linkedArcs) {//for each arc linked to the removed node
			deleteArc(drawArc);//delete that arc
		}
		drawNodes.remove(removed.getId());//remove the node
	}

	public void insertNode(Point coords, Rectangle constraint, NodeBean node) {
		boolean seguir = false;
		Collection<DrawNode> nodes = drawNodes.values();
		do {
			seguir = false;
			if (!constraint.contains(new DrawNode(node, coords).getShape().getBounds())) {
				seguir = true;
				if (coords.x < constraint.x) {
					coords.move(coords.x + 20, coords.y);
				}
				if (coords.x > constraint.width) {
					coords.move(coords.x - 20, coords.y);
				}
				if (coords.y < constraint.y) {
					coords.move(coords.x, coords.y + 20);
				}
				if (coords.y > constraint.height) {
					coords.move(coords.x, coords.y - 20);
				}
			}
			for (DrawNode n : nodes) {
				if (n.getShape().contains(coords)) {
					seguir = true;
				}
			}
			if (seguir) {
				int movex = (int) Math.round(Math.random() * 10);
				int movey = (int) Math.round(Math.random() * 10);
				if (movex % 2 == 0) {
					movex = -movex;
				}
				if (movey % 2 == 0) {
					movey = -movey;
				}
				coords.move(coords.x + movex, coords.y + movey);
			}

		} while (seguir);
		drawNodes.put(node.getId(), new DrawNode(node, coords));

	}

	public void updateNode(NodeBean node) {
		drawNodes.get(node.getId()).setTag(node.getLabel());

		//DrawArc updated = dNodes.get(node.getIndex());
		//Attribute oldOne = updated.getShowedAttribute();
		//updated.setShowedAttribute(new Attribute(oldOne.getName(), arc.getAttribute(oldOne.getName()).getValue()));
	}

	public void colorNode(NodeBean node, Color color) {
		drawNodes.get(node.getId()).setColor(color);
	}

	public void updateAll(IGraph graph) {//TODO
		throw new UnsupportedOperationException("Not supported yet.");
	}

	public void setSource(NodeBean source) {
		Collection<DrawNode> c = drawNodes.values();
		DrawSourceNode last = null;
		for (DrawNode drawNode : c) {
			if (drawNode instanceof DrawSourceNode) {
				last = (DrawSourceNode) drawNode;
			}
		}
		if (last != null) {
			drawNodes.put(last.getId(), last.toDrawNode());
		}
		drawNodes.put(source.getId(), new DrawSourceNode(drawNodes.get(source.getId())));
	}

	public void unsetSource(NodeBean source) {
		drawNodes.put(source.getId(), ((DrawSourceNode) drawNodes.get(source.getId())).toDrawNode());
	}

	public void setSink(NodeBean node) {
		Collection<DrawNode> c = drawNodes.values();
		DrawSinkNode last = null;
		for (DrawNode drawNode : c) {
			if (drawNode instanceof DrawSinkNode) {
				last = (DrawSinkNode) drawNode;
			}
		}
		if (last != null) {
			drawNodes.put(last.getId(), last.toDrawNode());
		}
		drawNodes.put(node.getId(), new DrawSinkNode(drawNodes.get(node.getId())));
	}

	public void unsetSink(NodeBean sink) {
		drawNodes.put(sink.getId(), ((DrawSinkNode) drawNodes.get(sink.getId())).toDrawNode());
	}

	public void addArcAttribute(String name, Integer defaultValue) {
		if (!this.drawArcs.isEmpty() && drawArcs.get(drawArcs.firstKey()).getShowedAttribute() == null) {
			Collection<Integer> c = drawArcs.keySet();
			for (Integer key : c) {
				drawArcs.get(key).setShowedAttribute(new Attribute(name, defaultValue));
			}
		}
	}

	public void updateArcAttribute(String previousName, String newName, Integer newValue) {
		if (!this.drawArcs.isEmpty()) {
			if (drawArcs.get(drawArcs.firstKey()).getShowedAttribute() != null) {
				Collection<Integer> c = drawArcs.keySet();
				for (Integer key : c) {
					Attribute oldAttribute = drawArcs.get(key).getShowedAttribute();
					drawArcs.get(key).setShowedAttribute(new Attribute(newName, oldAttribute.getValue()));
				}
			} else {//This else should never be reached... PENDING
				addArcAttribute(newName, newValue);
			}
		}
	}

	public void removeArcAttribute(String name) {
		if (!this.drawArcs.isEmpty()) {
			if (drawArcs.get(drawArcs.firstKey()).getShowedAttribute() != null) {
				if (drawArcs.get(drawArcs.firstKey()).getShowedAttribute().getName().equalsIgnoreCase(name)){
					Collection<Integer> c = drawArcs.keySet();
					for (Integer key : c) {
						drawArcs.get(key).setShowedAttribute(null);
					}
				}
			}
		}
	}

	public void addNodeAttribute(String name, Integer defaultValue) {
		if (!this.drawNodes.isEmpty() && drawNodes.get(drawNodes.firstKey()).getShowedAttribute() == null) {
			Collection<Integer> c = drawNodes.keySet();
			for (Integer key : c) {
				drawNodes.get(key).setShowedAttribute(new Attribute(name, defaultValue));
			}
		}
	}

	public void updateNodeAttribute(String previousName, String newName, Integer newValue) {
		if (!this.drawNodes.isEmpty()) {
			if (drawNodes.get(drawNodes.firstKey()).getShowedAttribute() != null) {
				Collection<Integer> c = drawNodes.keySet();
				for (Integer key : c) {
					Attribute oldAttribute = drawNodes.get(key).getShowedAttribute();
					drawNodes.get(key).setShowedAttribute(new Attribute(newName, oldAttribute.getValue()));
				}
			} else {//This else should never be reached... PENDING
				addNodeAttribute(newName, newValue);
			}
		}
	}

	public void removeNodeAttribute(String name) {
		if (!this.drawNodes.isEmpty()) {
			if (drawNodes.get(drawNodes.firstKey()).getShowedAttribute() != null) {
				if (drawNodes.get(drawNodes.firstKey()).getShowedAttribute().getName().equalsIgnoreCase(name)){
					Collection<Integer> c = drawNodes.keySet();
					for (Integer key : c) {
						drawNodes.get(key).setShowedAttribute(null);
					}
				}
			}
		}
	}
	//</editor-fold>
}
